﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QFlow.Data;
using QFlow.Core;
using System.Data.Linq;
using QFlow.Windows.Forms;
using System.Windows.Forms;

namespace QFlow.Windows
{
    public class PurchaseOrderManager
    {
        static QFlowDbContext _Context;
        public static QFlowDbContext Context
        {
            get
            {
                return _Context;
            }
            set
            {
                _Context = value;
            }
        }

        #region [ Calculations ]

        public static double CalculateDiscountPercent(double value, double totalValue)
        {
            return (value / totalValue * 100);
        }

        public static double CalculateDicountValue(double percent, double totalValue)
        {
            return (totalValue * percent / 100);
        }

        public static double CaluclateItemDiscount(double originalPrice, double curentPrice)
        {
            return (originalPrice - curentPrice) * 100 / originalPrice;
        }

        public static double CalculateItemPrice(double originalPrice, double discount)
        {
            return originalPrice - (discount * originalPrice / 100);
        }

        public static double CalculateItemValue(double price, double quantity)
        {
            return price * quantity;
        }

        public static double CalculateItemValue(PurchaseItem item)
        {
            return item.UnitPrice * item.Quantity;
        }

        public static double CalculateOrderValue(PurchaseOrder order, double discount)
        {
            double value = 0;

            foreach (var item in order.PurchaseItems)
            {
                if (!Context.ItemTypes.IsReturned(item.ItemTypeID))
                    if (item.PurchaseItems.Count > 0)
                        value += CalculateItemValue(item.UnitPrice, item.Quantity - item.PurchaseItems[0].Quantity);
                    else value += CalculateItemValue(item);
            }
            return (value - CalculateDicountValue(discount, value));
        }

        public static double CalculateOrderValueWithoutDiscount(PurchaseOrder order)
        {
            double value = 0;
            foreach (var item in order.PurchaseItems)
            {
                if (!Context.ItemTypes.IsReturned(item.ItemTypeID))
                {
                    if (item.PurchaseItems.Count > 0)
                        value += CalculateItemValue(item.UnitPrice, item.Quantity - item.PurchaseItems[0].Quantity);
                    else value += CalculateItemValue(item);
                }
            }
            return value;
        }

        public static double CalculatePaidAmount(PurchaseOrder order)
        {
            double paid = 0;
            foreach (var item in order.AccountingTransactions)
            {
                if (item.Income)
                    paid -= item.Amount;
                else paid += item.Amount;
            }
            return paid;
        }

        #endregion

        /// <summary>
        /// This functions check to see if all the items are set to paid status, the order will be set to paid status
        /// </summary>
        public static void SetOrderStatus(PurchaseOrder order, double discount)
        {
            bool allReceived = true;
            foreach (var item in order.PurchaseItems)
            {
                if (!Context.ItemTypes.IsReceived(item.ItemTypeID))
                {
                    allReceived = false;
                    break;
                }
            }
            bool nonReceived = true;
            foreach (var item in order.PurchaseItems)
            {
                if (Context.ItemTypes.IsReceived(item.ItemTypeID))
                {
                    nonReceived = false;
                    break;
                }
            }
            //check for paid amount and set the status text
            double paidAmount = PurchaseOrderManager.CalculatePaidAmount(order);
            if (paidAmount > 0)
            {
                double orderAmount = CalculateOrderValue(order, discount);
                if (allReceived && paidAmount >= orderAmount)
                {
                    order.OrderStatus = Context.OrderStatus.GetClosedStatus();
                    if (!order.IsAttached())
                        order.PaymentMethod = Context.PaymentMethods.GetCashMethod();
                }
                else if (nonReceived && paidAmount >= orderAmount)
                {
                    order.OrderStatus = Context.OrderStatus.GetPendingStatus();
                    if (!order.IsAttached())
                        order.PaymentMethod = Context.PaymentMethods.GetInAdvanceMethod();
                }
                else if (!allReceived && paidAmount < orderAmount)
                {
                    order.OrderStatus = Context.OrderStatus.GetPendingStatus();
                    order.PaymentMethod = Context.PaymentMethods.GetPendingMethod();
                }
            }
        }

        public static int CheckOrderPayments(PurchaseOrder order, double discount)
        {
            double paidAmount = PurchaseOrderManager.CalculatePaidAmount(order);
            double orderAmount = PurchaseOrderManager.CalculateOrderValue(order, discount);
            if (paidAmount < orderAmount)
                return -1;
            else if (paidAmount > orderAmount)
                return 1;
            return 0;
        }

        #region [ Items ]

        public static void CreatePurchaseItem(double quantity, double price, Product product,
            double discount, double taxes, string vendorCode, string notes, DateTime expirationDate, PurchaseOrder order)
        {
            PurchaseItem item = new PurchaseItem()
            {
                Quantity = quantity,
                UnitPrice = price,
                Product = product,
                Timestamp = DateTime.Now,
                DiscountPercent = discount,
                TaxesPercent = taxes,
                ItemType = Context.ItemTypes.GetPendingType(),
                VendorCode = !string.IsNullOrEmpty(vendorCode) ? vendorCode : null,
                Notes = !string.IsNullOrEmpty(notes) ? notes : null,
                ExpirationDate = expirationDate,
            };
            order.PurchaseItems.Add(item);
        }

        public static void UpdatePurchaseItem(PurchaseItem item, double quantity, double price, Product product,
            double discount, double taxes, string vendorCode, string notes, DateTime expirationDate)
        {
            if (Context.ItemTypes.IsReceived(item.ItemTypeID))
            {
                //remove the item's old quantity
                if (!item.Product.IsNonStockable)
                    item.Product.StockableProduct.CurrentQuantity -= item.Quantity;
            }

            item.Quantity = quantity;
            item.UnitPrice = price;
            item.Product = product;
            item.TaxesPercent = taxes;
            item.DiscountPercent = discount;

            item.User = Authentication.CurrentUser as User;

            if (!string.IsNullOrEmpty(vendorCode))
                item.VendorCode = vendorCode;
            if (!string.IsNullOrEmpty(notes))
                item.Notes = notes;

            item.ExpirationDate = expirationDate;

            item.Timestamp = DateTime.Now;

            if (Context.ItemTypes.IsReceived(item.ItemTypeID))
            {
                //add the item's new quantity
                if (!item.Product.IsNonStockable)
                    item.Product.StockableProduct.CurrentQuantity += item.Quantity;
            }
        }

        public static void RemovePurchaseItem(PurchaseItem item, PurchaseOrder order)
        {
            if (Context.ItemTypes.IsReceived(item.ItemTypeID))
            {
                if (!item.Product.IsNonStockable)
                    item.Product.StockableProduct.CurrentQuantity -= item.Quantity;
            }

            if (item.IsAttached())
                order.PurchaseItems.Remove(item);
            else
            {
                Context.RemoveRecordFromInsertions(item);
                order.PurchaseItems.Remove(item);
            }
        }

        public static void ReceiveItem(PurchaseItem item)
        {
            item.ItemType = Context.ItemTypes.GetReceivedType();

            if (!item.Product.IsNonStockable)
                item.Product.StockableProduct.CurrentQuantity += item.Quantity;
        }

        public static void ReceiveItems(PurchaseOrder order)
        {
            foreach (var item in order.PurchaseItems)
            {
                if (Context.ItemTypes.IsPending(item.ItemTypeID))
                {
                    item.ItemType = Context.ItemTypes.GetReceivedType();

                    if (!item.Product.IsNonStockable)
                        item.Product.StockableProduct.CurrentQuantity += item.Quantity;
                }
            }
        }

        #endregion

        #region [ Payments ]

        public static void CreatePayment(double amount, Account selectedAccount, PurchaseOrder order)
        {
            AccountingTransaction transaction = new AccountingTransaction()
            {
                Amount = amount,
                Income = false,
                Account = selectedAccount,
                TimeStamp = DateTime.Now
            };

            order.AccountingTransactions.Add(transaction);

            Accounting.PurchaseCredit += amount;
            transaction.Account.Amount -= transaction.Amount;
        }

        public static void UpdatePayment(AccountingTransaction transaction, double amount, Account selectedAccount, PurchaseOrder order)
        {
            Accounting.PurchaseCredit -= transaction.Amount;
            transaction.Account.Amount += transaction.Amount;

            transaction.Amount = amount;
            transaction.TimeStamp = DateTime.Now;
            transaction.Account = selectedAccount;

            Accounting.PurchaseCredit += amount;
            selectedAccount.Amount -= amount;
        }

        public static void DeletePayment(AccountingTransaction transaction, PurchaseOrder order)
        {
            Accounting.PurchaseCredit -= transaction.Amount;
            transaction.Account.Amount += transaction.Amount;

            Context.RemoveRecordFromInsertions(transaction);
            order.AccountingTransactions.Remove(transaction);
        }

        public static void PayOrder(PurchaseOrder order, double discount)
        {
            double oldpaidAmount = 0;
            foreach (var item in order.AccountingTransactions)
            {
                if (item.IsAttached())
                {
                    if (item.Income == true)
                        oldpaidAmount -= item.Amount;
                    else oldpaidAmount += item.Amount;
                }
            }
            double orderValue = CalculateOrderValue(order, discount);

            NewPayment frm = new NewPayment(Context, false, orderValue - oldpaidAmount);
            if (frm.ShowDialog() == DialogResult.OK)
            {
                if (oldpaidAmount < orderValue)
                {
                    for (int i = order.AccountingTransactions.Count - 1; i >= 0; i--)
                    {
                        var item = order.AccountingTransactions[i];
                        if (!item.IsAttached())
                        {
                            Accounting.PurchaseCredit -= item.Amount;
                            item.Account.Amount += item.Amount;

                            order.AccountingTransactions.Remove(item);
                        }
                    }
                }
                CreatePayment(frm.Amount, frm.SelectedAccount, order);
            }
        }

        #endregion

        public static void CreatePurchaseOrder(PurchaseOrder order, string vendorCode, double discount, Vendor vendor)
        {
            if (!string.IsNullOrEmpty(vendorCode))
                order.VendorInvoiceID = vendorCode;
            order.Date = DateTime.Now;
            order.DiscountPercent = discount;
            order.Vendor = vendor;

            foreach (var item in order.PurchaseItems)
            {
                item.Timestamp = DateTime.Now;
                item.User = Context.Users.GetCurrentUser();
            }

            foreach (var item in order.AccountingTransactions)
            {
                item.Account1 = vendor.Accounts.First();
                //cut the amount from the vendor
                item.Account1.Amount -= item.Amount;

                item.TimeStamp = DateTime.Now;
                item.User = Context.Users.GetCurrentUser();
            }

            //add the order's value to the vendor's credit
            vendor.Accounts.First().Amount += CalculateOrderValue(order, order.DiscountPercent);

            Context.PurchaseOrders.InsertOnSubmit(order);
            Context.SubmitChanges();
        }

        public static void UpdatePurchaseOrder(PurchaseOrder order, string vendorCode, double discount, Vendor vendor)
        {
            if (!string.IsNullOrEmpty(vendorCode))
                order.VendorInvoiceID = vendorCode;

            UpdateVendorCredit(order, discount, vendor);

            foreach (var item in order.PurchaseItems)
            {
                if (!item.IsAttached())
                {
                    item.Timestamp = DateTime.Now;
                    item.User = Context.Users.GetCurrentUser();
                }
            }

            foreach (var item in order.AccountingTransactions)
            {
                if (!item.IsAttached())
                {
                    item.Account1 = order.Vendor.Accounts.First();
                    //cut the amount from the vendor
                    item.Account1.Amount -= item.Amount;

                    item.TimeStamp = DateTime.Now;
                    item.User = Context.Users.GetCurrentUser();
                }
            }
            Context.SubmitChanges();
        }

        private static void UpdateVendorCredit(PurchaseOrder order, double discount, Vendor vendor)
        {
            QFlowDbContext context = new QFlowDbContext();
            PurchaseOrder oldOrder = context.PurchaseOrders.Single(o => o.PurchaseOrderID == order.PurchaseOrderID);
            order.Vendor.Accounts.First().Amount -= CalculateOrderValue(oldOrder, oldOrder.DiscountPercent);

            order.Vendor = vendor;
            order.DiscountPercent = discount;

            order.Vendor.Accounts.First().Amount += CalculateOrderValue(order, order.DiscountPercent);
        }

        public static void CancelPurchaseOrder(PurchaseOrder order)
        {
            double paidAmount = CalculatePaidAmount(order);
            if (paidAmount > 0)
            {
                NewPayment frm = new NewPayment(Context, true, paidAmount);
                if (frm.ShowDialog() == DialogResult.OK)
                {
                    AccountingTransaction transaction = new AccountingTransaction()
                    {
                        Amount = frm.Amount,
                        Income = true,
                        Account = order.Vendor.Accounts.First(),
                        Account1 = frm.SelectedAccount,
                        User = Context.Users.GetCurrentUser(),
                        TimeStamp = DateTime.Now,
                    };

                    order.AccountingTransactions.Add(transaction);

                    Accounting.PurchaseReturn += transaction.Amount;
                    //system account
                    transaction.Account1.Amount += transaction.Amount;

                    foreach (var item in order.PurchaseItems)
                    {
                        if (Context.ItemTypes.IsReceived(item.ItemTypeID))
                        {
                            //set the current quantity of the purchasd product
                            if (!item.Product.IsNonStockable)
                                item.Product.StockableProduct.CurrentQuantity -= item.Quantity;
                        }
                        else if (Context.ItemTypes.IsReturned(item.ItemTypeID))
                        {
                            //set the current quantity of the purchasd product
                            if (!item.Product.IsNonStockable)
                                item.Product.StockableProduct.CurrentQuantity += item.Quantity;
                        }
                    }

                    order.Date = DateTime.Now;
                    order.OrderStatus = Context.OrderStatus.GetCanceledStatus();
                    Context.SubmitChanges();
                }
            }
            else
            {
                foreach (var item in order.PurchaseItems)
                {
                    if (Context.ItemTypes.IsReceived(item.ItemTypeID))
                    {
                        //set the current quantity of the purchasd product
                        if (!item.Product.IsNonStockable)
                            item.Product.StockableProduct.CurrentQuantity -= item.Quantity;
                    }
                    else if (Context.ItemTypes.IsReturned(item.ItemTypeID))
                    {
                        //set the current quantity of the purchasd product
                        if (!item.Product.IsNonStockable)
                            item.Product.StockableProduct.CurrentQuantity += item.Quantity;
                    }
                }

                order.Date = DateTime.Now;
                order.OrderStatus = Context.OrderStatus.GetCanceledStatus();
                Context.SubmitChanges();
            }
        }

        #region [ Return Purchase Order]

        public static PurchaseItem ReturnItem(PurchaseItem item, double quantity, double orderDiscount)
        {
            PurchaseItem returned = item.Clone();
            returned.ItemType = Context.ItemTypes.GetReturnedType();
            returned.User = Context.Users.GetCurrentUser();
            returned.PurchaseItem1 = item;
            returned.UnitPrice = item.UnitPrice - CalculateDicountValue(orderDiscount, item.UnitPrice);
            returned.Quantity = quantity;

            return returned;
        }

        public static bool CheckReturnItems(List<PurchaseItem> returnItems, PurchaseItem item)
        {
            foreach (var returnItem in returnItems)
            {
                if (returnItem.ParentPurchaseItemID == item.PurchaseItemID)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Calculates the maximum return quantity for this item.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="returnItems"></param>
        /// <returns></returns>
        public static double GetMaximumReturnQuantity(PurchaseItem item)
        {
            double quantity = 0;
            quantity += item.Quantity;
            quantity -= item.Sold;
            return quantity;
        }

        public static double GetAvailableReturnQuantity(PurchaseItem returnedItem)
        {
            PurchaseItem item = returnedItem.PurchaseItem1;
            double quantity = 0;
            quantity += item.Quantity;
            quantity -= item.Sold;

            return quantity;
        }

        public static double GetMinimumReturnQuantity(PurchaseItem returndItem)
        {
            if (!returndItem.IsAttached())
            {
                return 0.1;
            }
            else
            {
                return GetOriginalItem(returndItem.PurchaseItemID).Quantity;
            }
        }

        public static PurchaseItem GetOriginalItem(int purchaseItemId)
        {
            return new QFlowDbContext().PurchaseItems.Where(item => item.PurchaseItemID == purchaseItemId).First();
        }

        public static double SaveReturenedOrder(PurchaseOrder order, List<PurchaseItem> returnedItems)
        {
            double returnedValue = 0;
            foreach (var item in returnedItems)
            {
                if (!item.IsAttached())
                {
                    order.PurchaseItems.Add(item);
                    returnedValue += CalculateItemValue(item);
                    item.Timestamp = DateTime.Now;

                    if (!item.Product.IsNonStockable)
                    {
                        item.Product.StockableProduct.CurrentQuantity -= item.Quantity;
                    }
                }
                else if (item.IsDirty())
                {
                    double quantity = item.Quantity - GetOriginalItem(item.PurchaseItemID).Quantity;
                    returnedValue += CalculateItemValue(item.UnitPrice, quantity);
                    item.Timestamp = DateTime.Now;

                    if (!item.Product.IsNonStockable)
                    {
                        item.Product.StockableProduct.CurrentQuantity -= quantity;
                    }
                }

            }
            if (returnedValue > 0)
            {
                NewPayment frm = new NewPayment(Context, true, returnedValue);
                if (frm.ShowDialog() == DialogResult.OK)
                {
                    AccountingTransaction transaction = new AccountingTransaction()
                    {
                        Amount = frm.Amount,
                        Income = true,
                        Account = order.Vendor.Accounts.First(),
                        Account1 = frm.SelectedAccount,
                        User = Context.Users.GetCurrentUser(),
                        TimeStamp = DateTime.Now,
                    };

                    order.AccountingTransactions.Add(transaction);

                    Accounting.PurchaseReturn += transaction.Amount;
                    transaction.Account1.Amount += transaction.Amount;

                    Context.SubmitChanges();
                }
            }
            return returnedValue;
        }

        #endregion
    }
}
